package com.itcam.maker.template;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.itcam.maker.meta.Meta;
import com.itcam.maker.meta.enums.FileGenerateTypeEnum;
import com.itcam.maker.meta.enums.FileTypeEnum;
import com.itcam.maker.template.enums.FileFilterRangeEnum;
import com.itcam.maker.template.enums.FileFilterRuleEnum;
import com.itcam.maker.template.model.*;

import java.io.File;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author : Cammy.Wu
 * Description : 
 */

public class TemplateMaker {

    public static void main(String[] args) {
        Meta meta = new Meta();
        meta.setName("acm-template-generator");
        meta.setDescription("ACM 示例模板生成器");

        String projectPath = System.getProperty("user.dir");
        String originProjectPath = new File(projectPath).getParent() + File.separator + "iah-code-demo-projects/springboot-init";
        String inputFilePath1 = "src/main/java/com/itcam/springbootinit/common";
//        String inputFilePath2 = "src/main/java/com/itcam/springbootinit/constant";
//        List<String> inputFilePathList = Arrays.asList(inputFilePath1, inputFilePath2);
//
//        // 模型参数信息（首次）
////        Meta.ModelConfig.ModelInfo modelInfo = new Meta.ModelConfig.ModelInfo();
////        modelInfo.setFieldName("outputText");
////        modelInfo.setType("String");
////        modelInfo.setDefaultValue("sum = ");
//
//        // 模型参数信息（第二次）
//        Meta.ModelConfig.ModelInfo modelInfo = new Meta.ModelConfig.ModelInfo();
//        modelInfo.setFieldName("className");
//        modelInfo.setType("String");
//


        String inputFilePath2 = "src/main/resources/application.yml";

        // 模型参数配置
        TemplateMakerModelConfig templateMakerModelConfig = new TemplateMakerModelConfig();

        // - 模型组配置
        TemplateMakerModelConfig.ModelGroupConfig modelGroupConfig = new TemplateMakerModelConfig.ModelGroupConfig();
        modelGroupConfig.setGroupKey("mysql");
        modelGroupConfig.setGroupName("数据库配置");
        templateMakerModelConfig.setModelGroupConfig(modelGroupConfig);

        // - 模型配置
        TemplateMakerModelConfig.ModelInfoConfig modelInfoConfig1 = new TemplateMakerModelConfig.ModelInfoConfig();
        modelInfoConfig1.setFieldName("url");
        modelInfoConfig1.setType("String");
        modelInfoConfig1.setDefaultValue("jdbc:mysql://localhost:3306/iah-backend");
        modelInfoConfig1.setReplaceText("jdbc:mysql://localhost:3306/iah-backend");

        TemplateMakerModelConfig.ModelInfoConfig modelInfoConfig2 = new TemplateMakerModelConfig.ModelInfoConfig();
        modelInfoConfig2.setFieldName("username");
        modelInfoConfig2.setType("String");
        modelInfoConfig2.setDefaultValue("root");
        modelInfoConfig2.setReplaceText("root");

        List<TemplateMakerModelConfig.ModelInfoConfig> modelInfoConfigList = Arrays.asList(modelInfoConfig1, modelInfoConfig2);
        templateMakerModelConfig.setModels(modelInfoConfigList);

        //        // 替换变量（首次）
////        String searchStr = "Sum: ";
//        // 替换变量（第二次）
        String searchStr = "BaseResponse";

        // 文件过滤
        TemplateMakerFileConfig templateMakerFileConfig = new TemplateMakerFileConfig();
        TemplateMakerFileConfig.FileInfoConfig fileInfoConfig1 = new TemplateMakerFileConfig.FileInfoConfig();
        fileInfoConfig1.setPath(inputFilePath1);
        List<FileFilterConfig> fileFilterConfigList = new ArrayList<>();
        FileFilterConfig fileFilterConfig = FileFilterConfig.builder()
                .range(FileFilterRangeEnum.FILE_NAME.getValue())
                .rule(FileFilterRuleEnum.CONTAINS.getValue())
                .value("Base")
                .build();
        fileFilterConfigList.add(fileFilterConfig);
        fileInfoConfig1.setFilterConfigList(fileFilterConfigList);

        TemplateMakerFileConfig.FileInfoConfig fileInfoConfig2 = new TemplateMakerFileConfig.FileInfoConfig();
        fileInfoConfig2.setPath(inputFilePath2);
        templateMakerFileConfig.setFiles(Arrays.asList(fileInfoConfig1, fileInfoConfig2));

        // 分组配置
        TemplateMakerFileConfig.FileGroupConfig fileGroupConfig = new TemplateMakerFileConfig.FileGroupConfig();
        fileGroupConfig.setCondition("outputText");
        fileGroupConfig.setGroupKey("test");
        fileGroupConfig.setGroupName("测试分组");
        templateMakerFileConfig.setFileGroupConfig(fileGroupConfig);

        long id = makeTemplate(meta, originProjectPath, templateMakerFileConfig, templateMakerModelConfig, null, 1735281524670181376L);
        System.out.println(id);

    }

    /**
     * 制作模板
     *
     * @param newMeta                   新的元数据信息 📄
     * @param originProjectPath         原始项目的路径 📂
     * @param templateMakerFileConfig   模板制作的文件配置信息 📁
     * @param templateMakerModelConfig  模板制作的模型配置信息 🧩
     * @param templateMakerOutputConfig 模板制作的输出配置信息 🖨️
     * @param id                        模板的唯一标识，如果为 null 会自动生成一个哦 🔢
     * @return 制作模板的唯一标识 🆔
     */
    public static long makeTemplate(
            Meta newMeta,
            String originProjectPath,
            TemplateMakerFileConfig templateMakerFileConfig,
            TemplateMakerModelConfig templateMakerModelConfig,
            TemplateMakerOutputConfig templateMakerOutputConfig,
            Long id) {
        // 没有 id 则生成一个新的 id 哦，就像给模板发个独一无二的身份证一样 🔑
        if (id == null) {
            id = IdUtil.getSnowflakeNextId();
        }

        // 复制目录
        // 获取当前用户工作目录，就像找到我们的大本营一样 🏠
        String projectPath = System.getProperty("user.dir");
        // 临时目录的路径，用于存放临时文件哦 📦
        String tempDirPath = projectPath + File.separator + ".temp";
        // 模板存放的具体路径，根据 id 来区分不同的模板 📋
        String templatePath = tempDirPath + File.separator + id;

        // 是否为首次制作模板
        // 目录不存在，则是首次制作，就像开辟一块新的土地一样 🌱
        if (!FileUtil.exist(templatePath)) {
            // 创建模板目录
            FileUtil.mkdir(templatePath);
            // 把原始项目复制到模板目录，就像把一个房子原样复制一份 🏠➡️🏠
            FileUtil.copy(originProjectPath, templatePath, true);
        }

        // 1.输入信息
        // 输入文件信息
        // 找到模板目录下的第一个子目录作为源根路径，就像找到宝藏的入口一样 🚪
        String sourceRootPath = FileUtil.loopFiles(new File(templatePath), 1, null)
                .stream()
                .filter(File::isDirectory)
                .findFirst()
                .orElseThrow(RuntimeException::new)
                .getAbsolutePath();
        // 把路径中的反斜杠替换成正斜杠，让路径在不同系统下都能正常使用哦 ➡️
        sourceRootPath = sourceRootPath.replaceAll("\\\\", "/");
        // 获取文件配置信息列表，就像拿到一份文件清单一样 📜
        List<TemplateMakerFileConfig.FileInfoConfig> fileConfigInfoList = templateMakerFileConfig.getFiles();

        // - 本次新增的模型配置列表
        // 获取新增的模型配置列表，就像收集新的拼图碎片一样 🧩
        List<Meta.ModelConfig.ModelInfo> newModelInfoList = getModelInfoList(templateMakerModelConfig);

        // 2.生成文件模板
        // 根据文件配置和模型配置生成文件模板，就像用模具制作小物件一样 🔨
        List<Meta.FileConfig.FileInfo> newFileInfoList = makeFileTemplates(templateMakerFileConfig, templateMakerModelConfig, sourceRootPath);

        // 3.输出配置文件
        // 元信息文件的输出路径，用于保存模板的配置信息哦 📄
        String metaOutputPath = templatePath + File.separator + "meta.json";

        // 如果已有meta文件，说明不是第一次制作，则在meta基础上进行修改
        if (FileUtil.exist(metaOutputPath)) {
            // 读取旧的元信息文件，就像翻开之前的日记一样 📖
            Meta oldMeta = JSONUtil.toBean(FileUtil.readUtf8String(metaOutputPath), Meta.class);
            // 把新的元信息复制到旧的元信息中，忽略空值，就像把新的内容补充到日记里一样 📝
            BeanUtil.copyProperties(newMeta, oldMeta, CopyOptions.create().ignoreNullValue());
            newMeta = oldMeta;

            // 追加配置参数
            // 获取文件配置信息列表
            List<Meta.FileConfig.FileInfo> fileInfoList = newMeta.getFileConfig().getFiles();
            // 把新生成的文件信息添加到列表中，就像给队伍增加新成员一样 🧑‍🤝‍🧑
            fileInfoList.addAll(newFileInfoList);
            // 获取模型配置信息列表
            List<Meta.ModelConfig.ModelInfo> modelInfoList = newMeta.getModelConfig().getModels();
            // 把新的模型信息添加到列表中，就像给拼图增加新的部分一样 🧩
            modelInfoList.addAll(newModelInfoList);

            // 配置去重
            // 对文件配置信息进行去重，就像清理重复的物品一样 🧹
            newMeta.getFileConfig().setFiles(distinctFiles(fileInfoList));
            // 对模型配置信息进行去重，就像清理重复的拼图碎片一样 🧹
            newMeta.getModelConfig().setModels(distinctModels(modelInfoList));

        } else {
            // 3.1 构造配置参数
            // 创建一个新的文件配置对象，就像建造一个新的文件管理房间一样 🛖
            Meta.FileConfig fileConfig = new Meta.FileConfig();
            // 把文件配置对象设置到元信息中
            newMeta.setFileConfig(fileConfig);
            // 设置源根路径
            fileConfig.setSourceRootPath(sourceRootPath);
            // 创建一个文件信息列表，用于存放文件信息
            List<Meta.FileConfig.FileInfo> fileInfoList = new ArrayList<>();
            // 把文件信息列表设置到文件配置中
            fileConfig.setFiles(fileInfoList);
            // 把新生成的文件信息添加到列表中，就像把新的文件放到房间里一样 📂
            fileInfoList.addAll(newFileInfoList);

            // 创建一个新的模型配置对象，就像建造一个新的模型管理房间一样 🛖
            Meta.ModelConfig modelConfig = new Meta.ModelConfig();
            // 把模型配置对象设置到元信息中
            newMeta.setModelConfig(modelConfig);
            // 创建一个模型信息列表，用于存放模型信息
            List<Meta.ModelConfig.ModelInfo> modelInfoList = new ArrayList<>();
            // 把模型信息列表设置到模型配置中
            modelConfig.setModels(modelInfoList);
            // 把新的模型信息添加到列表中，就像把新的模型放到房间里一样 🗿
            modelInfoList.addAll(newModelInfoList);
        }

        // 3.2 额外的输出配置
        if (templateMakerOutputConfig != null) {
            // 文件外层和分组去重
            if (templateMakerOutputConfig.isRemoveGroupFilesFromRoot()) {
                // 获取文件配置信息列表
                List<Meta.FileConfig.FileInfo> fileInfoList = newMeta.getFileConfig().getFiles();
                // 对文件信息列表进行去重，去除分组文件在根目录的重复项，就像整理书架，把重复的书拿走一样 📚
                newMeta.getFileConfig().setFiles(TemplateMakerUtils.removeGroupFilesFromRoot(fileInfoList));
            }
        }

        // 3.3 输出元信息文件
        // 把元信息以漂亮的JSON格式写入文件，就像把日记整理好放到盒子里一样 📦
        FileUtil.writeUtf8String(JSONUtil.toJsonPrettyStr(newMeta), metaOutputPath);
        // 返回模板的唯一标识
        return id;
    }


    /**
     * 制作模板 🛠️
     *
     * @param templateMakerConfig 模板制作配置 📋
     * @return 制作模板的结果 🎉
     */
    public static long makeTemplate(TemplateMakerConfig templateMakerConfig) {
        // 📄 获取模板制作配置中的元数据
        Meta meta = templateMakerConfig.getMeta();
        // 📂 获取模板制作配置中的原始项目路径
        String originProjectPath = templateMakerConfig.getOriginProjectPath();
        // 📁 获取模板制作配置中的文件配置
        TemplateMakerFileConfig templateMakerFileConfig = templateMakerConfig.getFileConfig();
        // 🧩 获取模板制作配置中的模型配置
        TemplateMakerModelConfig templateMakerModelConfig = templateMakerConfig.getModelConfig();
        // 🖨️ 获取模板制作配置中的输出配置
        TemplateMakerOutputConfig templateMakerConfigOutputConfig = templateMakerConfig.getOutputConfig();
        // 🔢 获取模板制作配置中的ID
        Long id = templateMakerConfig.getId();

        // 🌟 调用另一个重载的makeTemplate方法，传入获取到的参数
        return makeTemplate(meta, originProjectPath, templateMakerFileConfig, templateMakerModelConfig, templateMakerConfigOutputConfig, id);
    }

    /**
     * 文件去重 🗂️
     *
     * @param fileInfoList 包含文件信息的列表 📑
     * @return 去重后的文件信息列表 ✅
     */
    private static List<Meta.FileConfig.FileInfo> distinctFiles(List<Meta.FileConfig.FileInfo> fileInfoList) {
        // 策略：同分组内文件 merge，不同分组保留 📋
        // 1. 有分组的，以组为单位划分 👥
        Map<String, List<Meta.FileConfig.FileInfo>> groupKeyFileInfoListMap = fileInfoList
                .stream()
                .filter(fileInfo -> StrUtil.isNotBlank(fileInfo.getGroupKey()))
                .collect(
                        Collectors.groupingBy(Meta.FileConfig.FileInfo::getGroupKey)
                );

        // 2. 同组内的文件配置合并 🧐
        // 保存每个组对应的合并后的对象 map 🗺️
        Map<String, Meta.FileConfig.FileInfo> groupKeyMergedFileInfoMap = new HashMap<>();
        for (Map.Entry<String, List<Meta.FileConfig.FileInfo>> entry : groupKeyFileInfoListMap.entrySet()) {
            // 取出当前分组下的文件信息列表
            List<Meta.FileConfig.FileInfo> tempFileInfoList = entry.getValue();
            // 合并同组内文件，以输出路径去重
            List<Meta.FileConfig.FileInfo> newFileInfoList = new ArrayList<>(tempFileInfoList.stream()
                    .flatMap(fileInfo -> fileInfo.getFiles().stream())
                    .collect(
                            Collectors.toMap(Meta.FileConfig.FileInfo::getOutputPath, o -> o, (e, r) -> r)
                    ).values());

            // 使用新的 group 配置 🆕
            Meta.FileConfig.FileInfo newFileInfo = CollUtil.getLast(tempFileInfoList);
            newFileInfo.setFiles(newFileInfoList);
            String groupKey = entry.getKey();
            groupKeyMergedFileInfoMap.put(groupKey, newFileInfo);
        }

        // 3. 将文件分组添加到结果列表 📄
        List<Meta.FileConfig.FileInfo> resultList = new ArrayList<>(groupKeyMergedFileInfoMap.values());

        // 4. 将未分组的文件添加到结果列表 📎
        List<Meta.FileConfig.FileInfo> noGroupFileInfoList = fileInfoList.stream().filter(fileInfo -> StrUtil.isBlank(fileInfo.getGroupKey()))
                .collect(Collectors.toList());
        resultList.addAll(new ArrayList<>(noGroupFileInfoList.stream()
                .collect(
                        Collectors.toMap(Meta.FileConfig.FileInfo::getInputPath, o -> o, (e, r) -> r)
                ).values()));
        return resultList;
    }


    /**
     * 模型去重 🧩
     *
     * @param modelInfoList 包含模型信息的列表 📑
     * @return 去重后的模型信息列表 ✅
     */
    private static List<Meta.ModelConfig.ModelInfo> distinctModels(List<Meta.ModelConfig.ModelInfo> modelInfoList) {
        // 策略：同分组内模型 merge，不同分组保留 📋
        // 1. 有分组的，以组为单位划分 👥
        Map<String, List<Meta.ModelConfig.ModelInfo>> groupKeyModelInfoListMap = modelInfoList
                .stream()
                .filter(modelInfo -> StrUtil.isNotBlank(modelInfo.getGroupKey()))
                .collect(
                        Collectors.groupingBy(Meta.ModelConfig.ModelInfo::getGroupKey)
                );

        // 2. 同组内的模型配置合并 🤝
        // 保存每个组对应的合并后的对象 map 🗺️
        Map<String, Meta.ModelConfig.ModelInfo> groupKeyMergedModelInfoMap = new HashMap<>();
        for (Map.Entry<String, List<Meta.ModelConfig.ModelInfo>> entry : groupKeyModelInfoListMap.entrySet()) {
            // 取出当前分组下的模型信息列表
            List<Meta.ModelConfig.ModelInfo> tempModelInfoList = entry.getValue();
            // 合并同组内模型，以字段名去重
            List<Meta.ModelConfig.ModelInfo> newModelInfoList = new ArrayList<>(tempModelInfoList.stream()
                    .flatMap(modelInfo -> modelInfo.getModels().stream())
                    .collect(
                            Collectors.toMap(Meta.ModelConfig.ModelInfo::getFieldName, o -> o, (e, r) -> r)
                    ).values());

            // 使用新的 group 配置 🆕
            Meta.ModelConfig.ModelInfo newModelInfo = CollUtil.getLast(tempModelInfoList);
            newModelInfo.setModels(newModelInfoList);
            String groupKey = entry.getKey();
            groupKeyMergedModelInfoMap.put(groupKey, newModelInfo);
        }

        // 3. 将模型分组添加到结果列表 📄
        List<Meta.ModelConfig.ModelInfo> resultList = new ArrayList<>(groupKeyMergedModelInfoMap.values());

        // 4. 将未分组的模型添加到结果列表 📎
        List<Meta.ModelConfig.ModelInfo> noGroupModelInfoList = modelInfoList.stream().filter(modelInfo -> StrUtil.isBlank(modelInfo.getGroupKey()))
                .collect(Collectors.toList());
        resultList.addAll(new ArrayList<>(noGroupModelInfoList.stream()
                .collect(
                        Collectors.toMap(Meta.ModelConfig.ModelInfo::getFieldName, o -> o, (e, r) -> r)
                ).values()));
        return resultList;
    }


    /**
     * 制作文件模板
     *
     * 🛠️ 这个方法就像是一个神奇的工匠，根据提供的模型信息、源根路径和输入文件，精心打造出文件模板。
     *
     * @param templateMakerModelConfig 模型信息，就像是工匠手中的设计蓝图 📄
     * @param sourceRootPath 源根路径，好比是工匠工作坊的地址 📍
     * @param inputFile 输入文件，如同工匠要加工的原材料 📁
     * @param fileInfoConfig 文件信息配置，可看作是对最终作品的一些额外要求 📋
     * @return 文件信息，也就是工匠精心打造出来的作品的详细介绍 📑
     */
    private static Meta.FileConfig.FileInfo makeFileTemplate(
            TemplateMakerModelConfig templateMakerModelConfig,
            String sourceRootPath,
            File inputFile,
            TemplateMakerFileConfig.FileInfoConfig fileInfoConfig) {
        // 📌 要挖坑的文件绝对路径（用于制作模板）
        // 注意 win 系统需要对路径进行转义
        String fileInputAbsolutePath = inputFile.getAbsolutePath().replaceAll("\\\\", "/");
        // 🌟 生成的模板文件的绝对路径
        String fileOutputAbsolutePath = fileInputAbsolutePath + ".ftl";

        // 📈 文件输入输出相对路径（用于生成配置）
        // 计算文件相对于源根路径的输入路径
        String fileInputPath = fileInputAbsolutePath.replace(sourceRootPath + "/", "");
        // 计算文件相对于源根路径的输出路径
        String fileOutputPath = fileInputPath + ".ftl";

        // 📝 使用字符串替换，生成模板文件
        // 读取输入文件的内容
        String fileContent;
        // 如果已有模板文件，说明不是第一次制作，则在模板基础上再次挖坑
        boolean hasTemplateFile = FileUtil.exist(fileOutputAbsolutePath);
        if (hasTemplateFile) {
            // 👀 已有模板，从模板文件读取内容
            fileContent = FileUtil.readUtf8String(fileOutputAbsolutePath);
        } else {
            // 🍀 没有模板，从输入文件读取内容
            fileContent = FileUtil.readUtf8String(fileInputAbsolutePath);
        }

        // 🌈 支持多个模型：对同一个文件的内容，遍历模型进行多轮替换
        TemplateMakerModelConfig.ModelGroupConfig modelGroupConfig = templateMakerModelConfig.getModelGroupConfig();
        String newFileContent = fileContent;
        String replacement;
        for (TemplateMakerModelConfig.ModelInfoConfig modelInfoConfig : templateMakerModelConfig.getModels()) {
            // 不是分组
            if (modelGroupConfig == null) {
                // 📍 简单替换，生成替换文本
                replacement = String.format("${%s}", modelInfoConfig.getFieldName());
            } else {
                // 是分组
                String groupKey = modelGroupConfig.getGroupKey();
                // 注意挖坑要多一个层级
                // 📚 分组替换，生成替换文本
                replacement = String.format("${%s.%s}", groupKey, modelInfoConfig.getFieldName());
            }
            // 多次替换
            // 🔄 进行替换操作
            newFileContent = StrUtil.replace(newFileContent, modelInfoConfig.getReplaceText(), replacement);
        }


        // 📄 文件配置信息
        // 创建一个新的文件信息对象
        Meta.FileConfig.FileInfo fileInfo = new Meta.FileConfig.FileInfo();
        // 设置文件的输入路径
        fileInfo.setInputPath(fileInputPath);
        // 设置文件的输出路径
        fileInfo.setOutputPath(fileOutputPath);
        // 设置文件的生成条件
        fileInfo.setCondition(fileInfoConfig.getCondition());
        // 设置文件的类型为文件
        fileInfo.setType(FileTypeEnum.FILE.getValue());
        // 设置文件的生成类型为动态
        fileInfo.setGenerateType(FileGenerateTypeEnum.DYNAMIC.getValue());

//        // 和源文件一致，没有挖坑，则为静态生成
//        // 如果新文件内容与原文件内容相同，则为静态生成
//        if (!newFileContent.equals(fileContent)) {
//            // 输出路径=输入路径
//            fileInfo.setOutputPath(fileInputPath);
//            // 设置生成类型为静态
//            fileInfo.setGenerateType(FileGenerateTypeEnum.STATIC.getValue());
//        } else {
//            // 生成模板文件
//            // 设置生成类型为动态
//            fileInfo.setGenerateType(FileGenerateTypeEnum.DYNAMIC.getValue());
//            // 将新文件内容写入模板文件
//            FileUtil.writeUtf8String(newFileContent, fileOutputAbsolutePath);
//        }

        // 是否更改了文件内容
        boolean contentEquals = newFileContent.equals(fileContent);
        // 之前不存在模板文件，并且没有更改文件内容，则为静态生成
        if (!hasTemplateFile) {
            if (contentEquals) {
                // 输入路径没有FTL后缀
                // 📌 静态生成，设置输入路径
                fileInfo.setInputPath(fileInputPath);
                // 📌 静态生成，设置生成类型
                fileInfo.setGenerateType(FileGenerateTypeEnum.STATIC.getValue());
            } else {
                // 没有模板文件，需要挖坑，生成模板文件
                // 🛠️ 生成模板文件
                FileUtil.writeUtf8String(newFileContent, fileOutputAbsolutePath);
            }
        }
        // 🚀 返回文件信息对象
        return fileInfo;
    }

    /**
     * 生成文件模板
     *
     * 🎨 这个方法就像是一位创意满满的画家，根据提供的文件配置、模型配置和源根路径，精心绘制出一个个文件模板。
     *
     * @param templateMakerFileConfig 文件配置信息，如同画家手中的调色板 🎨，规定了文件的各种属性和条件。
     * @param templateMakerModelConfig 模型配置信息，恰似画家的绘画蓝图 📄，指导着模板生成的具体规则。
     * @param sourceRootPath 源根路径，好比是画家的工作室地址 📍，确定了文件操作的起始位置。
     * @return 生成的文件信息列表，也就是画家精心创作出来的一幅幅作品的详细介绍 📑。
     */
    private static List<Meta.FileConfig.FileInfo> makeFileTemplates(TemplateMakerFileConfig templateMakerFileConfig,
                                                                    TemplateMakerModelConfig templateMakerModelConfig,
                                                                    String sourceRootPath) {
        // 📋 创建一个空的文件信息列表，用于存放生成的文件信息，就像准备一个空的画框来装画作。
        List<Meta.FileConfig.FileInfo> newFileInfoList = new ArrayList<>();
        // 👀 非空校验：检查文件配置信息是否为空，如果为空则直接返回空列表，就像画家发现调色板是空的，无法作画。
        if (null == templateMakerFileConfig) {
            return newFileInfoList;
        }

        // 📜 获取文件配置信息中的文件列表，这是画家要处理的具体画作素材。
        List<TemplateMakerFileConfig.FileInfoConfig> fileConfigInfoList = templateMakerFileConfig.getFiles();
        // 👀 检查文件列表是否为空，如果为空则直接返回空列表，就像画家发现素材都没有，只能放弃。
        if (CollUtil.isEmpty(fileConfigInfoList)) {
            return newFileInfoList;
        }
        // 🌟 2.生成文件模板
        // 使用字符串替换，生成模板文件
        // 🔄 遍历文件配置信息列表，对每个文件进行处理，就像画家依次处理每一幅素材。
        for (TemplateMakerFileConfig.FileInfoConfig fileInfoConfig : fileConfigInfoList) {
            // 📍 获取输入文件的路径，这是素材的位置。
            String inputFilePath = fileInfoConfig.getPath();

            // 如果填的是相对路径，要改为绝对路径
            // 🔍 检查路径是否为相对路径，如果是则转换为绝对路径，就像画家要确定素材在工作室的准确位置。
            if (!inputFilePath.startsWith(sourceRootPath)) {
                inputFilePath = sourceRootPath + File.separator + inputFilePath;
            }

            // 📂 获取过滤后的文件列表（不会存在目录）
            // 使用过滤器筛选出符合条件的文件，就像画家挑选出合适的素材。
            List<File> fileList = FileFilter.doFilter(inputFilePath, fileInfoConfig.getFilterConfigList());
            // 👀 不处理已生成的FTL模板文件
            // 过滤掉已经是模板的文件，避免重复处理，就像画家不重复处理已经完成的画作。
            fileList = fileList.stream()
                    .filter(file -> !file.getAbsolutePath().endsWith(".ftl"))
                    .collect(Collectors.toList());
            // 🔄 遍历过滤后的文件列表，对每个文件生成模板，就像画家对每一幅素材进行创作。
            for (File file : fileList) {
                // 🛠️ 调用生成单个文件模板的方法，生成文件信息。
                Meta.FileConfig.FileInfo fileInfo = makeFileTemplate(templateMakerModelConfig, sourceRootPath, file, fileInfoConfig);
                // 📋 将生成的文件信息添加到列表中，就像把完成的画作放入画框。
                newFileInfoList.add(fileInfo);
            }

        }

        // 如果是文件组
        // 👀 检查是否存在文件组配置信息，如果有则进行特殊处理，就像画家要把一些画作组合成一个系列。
        TemplateMakerFileConfig.FileGroupConfig fileGroupConfig = templateMakerFileConfig.getFileGroupConfig();
        if (fileGroupConfig != null) {
            // 📄 获取文件组的条件、分组键和分组名称，这些是系列画作的标识。
            String condition = fileGroupConfig.getCondition();
            String groupKey = fileGroupConfig.getGroupKey();
            String groupName = fileGroupConfig.getGroupName();

            // 🌟 新增分组配置
            // 创建一个新的文件信息对象，用于表示文件组。
            Meta.FileConfig.FileInfo groupFileInfo = new Meta.FileConfig.FileInfo();
            // 📌 设置文件类型为分组，就像给系列画作一个特殊的标签。
            groupFileInfo.setType(FileTypeEnum.GROUP.getValue());
            // 📌 设置文件组的条件，这是系列画作的筛选规则。
            groupFileInfo.setCondition(condition);
            // 📌 设置文件组的分组键，这是系列画作的唯一标识。
            groupFileInfo.setGroupKey(groupKey);
            // 📌 设置文件组的分组名称，这是系列画作的名字。
            groupFileInfo.setGroupName(groupName);
            // 📂 文件全放到一个分组内
            // 将之前生成的文件信息列表放入文件组中，就像把画作都放入系列中。
            groupFileInfo.setFiles(newFileInfoList);
            // 📋 清空原文件信息列表，准备存放新的文件组信息。
            newFileInfoList = new ArrayList<>();
            // 📋 将文件组信息添加到列表中，就像把系列画作放入最终的展示区。
            newFileInfoList.add(groupFileInfo);
        }
        // 🚀 返回生成的文件信息列表，也就是画家完成的所有作品。
        return newFileInfoList;
    }

    /**
     * 🎁 获取模型信息列表，就像从魔法口袋里掏出对应的宝贝一样，把模板制作模型配置里的模型信息都拿出来。
     *
     * @param templateMakerModelConfig 模板制作模型配置，这是一个装满模型信息的“百宝箱” 🎁。
     * @return 模型信息列表，是从“百宝箱”里掏出来整理好的宝贝集合 📦。
     */
    private static List<Meta.ModelConfig.ModelInfo> getModelInfoList(TemplateMakerModelConfig templateMakerModelConfig) {
        // 🎒 先准备一个空的袋子，用来装我们要找的模型信息宝贝。
        List<Meta.ModelConfig.ModelInfo> newModelInfoList = new ArrayList<>();

        // 👀 瞅瞅这个“百宝箱”是不是空的，如果是空的，那就没啥可掏的，直接返回空袋子。
        if (templateMakerModelConfig == null) {
            return newModelInfoList;
        }

        // 🔓 打开“百宝箱”，把里面的模型信息配置都拿出来。
        List<TemplateMakerModelConfig.ModelInfoConfig> models = templateMakerModelConfig.getModels();
        // 👁️ 看看这些模型信息配置是不是啥都没有，如果啥都没有，还是返回空袋子。
        if (CollUtil.isEmpty(models)) {
            return newModelInfoList;
        }

        // 🪄 把每个模型信息配置都变个魔法，变成我们需要的 Meta.ModelConfig.ModelInfo 形式。
        List<Meta.ModelConfig.ModelInfo> inputModelInfoList = models.stream().map(modelInfoConfig -> {
            // 🪄 变魔法啦，先变出来一个新的 Meta.ModelConfig.ModelInfo 对象。
            Meta.ModelConfig.ModelInfo modelInfo = new Meta.ModelConfig.ModelInfo();
            // 🪄 用神奇的 BeanUtil.copyProperties 魔法，把模型信息配置的属性复制到新对象里。
            BeanUtil.copyProperties(modelInfoConfig, modelInfo);
            // ✨ 魔法变完，返回这个变好的对象。
            return modelInfo;
        }).collect(Collectors.toList());

        // 🔍 看看“百宝箱”里有没有模型组配置，就像看看有没有把宝贝分组放。
        TemplateMakerModelConfig.ModelGroupConfig modelGroupConfig = templateMakerModelConfig.getModelGroupConfig();
        // 📦 如果有分组放的宝贝
        if (null != modelGroupConfig) {
            // 🪄 再变一个新的 Meta.ModelConfig.ModelInfo 对象来代表这个组。
            Meta.ModelConfig.ModelInfo groupModelInfo = new Meta.ModelConfig.ModelInfo();
            // 🪄 把模型组配置的属性也复制到这个组对象里。
            BeanUtil.copyProperties(modelGroupConfig, groupModelInfo);
            // 📦 把变好的模型信息都放到这个组对象里面。
            groupModelInfo.setModels(inputModelInfoList);
            // 🎒 把这个组对象放到我们准备的袋子里。
            newModelInfoList.add(groupModelInfo);
        } else {
            // 📦 如果没有分组，那就把变好的模型信息都直接放到袋子里。
            newModelInfoList.addAll(inputModelInfoList);
        }
        // 🎉 袋子装满啦，把袋子返回出去。
        return newModelInfoList;
    }


}


